/** @file res_texture.cpp
	@brief Texture resource */

#include "res/res_texture.h"

using namespace G3;

//==============================================================================
// Constructors and Destructors
//==============================================================================
G3::TEXTURE::TEXTURE ():
	RESOURCE_BASE () {

	BitmapLocked = false;

	ID = -1;

	Filter = GTF_MIPMAP;

	Log = &MainLog;

	Bitmap = boost::shared_ptr<BITMAP> ();
}

// When texture is pushed back, STL deletes the Bitmap - a possible
// memory leak or double free
TEXTURE::~TEXTURE () {

}

//==============================================================================
// = operators
//==============================================================================
TEXTURE& TEXTURE::operator=(const TEXTURE &t) {
	ID = t.ID;
	Name = t.Name;

	BitmapLocked = t.BitmapLocked;
	Bitmap = t.Bitmap;
	Log = t.Log;

	Filter = t.Filter;

	return *this;
}

//==============================================================================
// Loads an image
//==============================================================================
bool G3::TEXTURE::LoadImage (STREAM *Stream, const std::string &filename) {
	Assert (Stream, "TEXTURE::LoadImage: Stream argument is a NULL pointer");
	Assert (filename.size () > 4, "TEXTURE::LoadImage: Invalid filename");

	if (filename [filename.size () - 4] == '.' &&
		tolower (filename [filename.size () - 3]) == 'b' &&
		tolower (filename [filename.size () - 2]) == 'm' &&
		tolower (filename [filename.size () - 1]) == 'p') {

		Bitmap = LoadBMP (Stream, Log);

		if (Bitmap == NULL || !Bitmap->IsValid ())
				return false;

		Name = filename;

		return true;
	}
	if (filename [filename.size () - 4] == '.' &&
		tolower (filename [filename.size () - 3]) == 'j' &&
		tolower (filename [filename.size () - 2]) == 'p' &&
		tolower (filename [filename.size () - 1]) == 'g') {

		Bitmap = LoadJPG (Stream, Log);

		if (Bitmap == boost::shared_ptr<BITMAP> () || !Bitmap->IsValid ())
				return false;

		Name = filename;

		return true;
	}
	if (filename [filename.size () - 4] == '.' &&
		tolower (filename [filename.size () - 3]) == 'p' &&
		tolower (filename [filename.size () - 2]) == 'n' &&
		tolower (filename [filename.size () - 1]) == 'g') {

		Bitmap = LoadPNG (Stream, Log);

		if (Bitmap == boost::shared_ptr<BITMAP> () || !Bitmap->IsValid ())
				return false;

		Name = filename;

		return true;
	}
	if (filename [filename.size () - 4] == '.' &&
		tolower (filename [filename.size () - 3]) == 'd' &&
		tolower (filename [filename.size () - 2]) == 'd' &&
		tolower (filename [filename.size () - 1]) == 's') {

		Bitmap = LoadDDS (Stream, Log);

		if (Bitmap == boost::shared_ptr<BITMAP> () || !Bitmap->IsValid ())
				return false;

		Name = filename;

		return true;
	}

	return false;
}

//==============================================================================
// Sets OpenGL texture filter
//==============================================================================
bool G3::TEXTURE::glFilter (TEXTURE_FILTER aFilter) {
	switch (Filter) {
		case GTF_NEAREST:
            glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
            glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
			break;

		case GTF_LINEAR:
            glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
            glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
			break;

		default:
		case GTF_MIPMAP:
            glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
            glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
			break;
	}
	return true;
}

//==============================================================================
// Builds an OpenGL texture from the Bitmap
//==============================================================================
int G3::TEXTURE::glBuild2D (TEXTURE_FILTER aFilter) {
	Assert (Bitmap != boost::shared_ptr<BITMAP> (), "TEXTURE::glBuild2D: Bitmap member is a NULL pointer");
	Assert (Bitmap->IsValid (), "TEXTURE::glBuild2D: Bitmap Data is invalid");

	uint ID2 = 0;
	static bool arbtcs = (GLEE_ARB_texture_compression);
	int mmWidth = 0, mmHeight = 0;
	int mmBlockSize = 0;
	int error = 0;
	long mmSize = 0;
	long mmOffset = 0;
	int i = 0;

	// Generate an OpenGL texture
	glGenTextures (1, &ID2);
	glBindTexture (GL_TEXTURE_2D, ID2);
	ID = ID2;

	if ((Bitmap->Width % 2 != 0) || (Bitmap->Height % 2 != 0)) {
		if (Log)
			Log->Report ("WARNING! The dimensions of this bitmap are not power of 2 (%ld x %ld)..\n", Bitmap->Width, Bitmap->Height);

        // Needs to be here - or non-pow2 textures make OpenGL crash
        glPixelStorei (GL_UNPACK_ALIGNMENT, 1);
	}

	// MipMaps are already precalculated?
	if (aFilter == GTF_MIPMAP && Bitmap->NumMipMaps > 1)
		Filter = GTF_LINEAR;
	else
		Filter = aFilter;

	// Set texture filtering
	glFilter (Filter);

	// Just in case the alignment is packed
	if (Bitmap->Flags & BITMAP_ALIGNMENT)
		glPixelStorei (GL_UNPACK_ALIGNMENT, Bitmap->PackedAlignment);

	if (Bitmap->Flags & BITMAP_COMPRESSED) {
		if (Bitmap->Compression == GL_COMPRESSED_RGBA_S3TC_DXT1_EXT) {
		    // DXT1
			mmBlockSize = 8;
		} else {
		    // DXT3 or DXT5
			mmBlockSize = 16;
		}
	}

	// Load mipmaps
	if (Filter != GTF_MIPMAP) {
		mmWidth = Bitmap->Width;
		mmHeight = Bitmap->Height;

		// Needed to avoid problems when some mipmap levels are missing..
		glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, Bitmap->NumMipMaps - 1);

		for (i=0; i<Bitmap->NumMipMaps; i++) {
			if (!mmWidth) mmWidth = 1;
			if (!mmHeight) mmHeight = 1;

			if ((Bitmap->Flags & BITMAP_COMPRESSED)) {
			    // Uploading compressed textures
				mmSize = ((mmWidth + 3) / 4) * ((mmHeight + 3) / 4) * mmBlockSize;

				if (arbtcs) {
				    // Uploading
					glCompressedTexImage2DARB (GL_TEXTURE_2D, i, Bitmap->Compression,
											   mmWidth, mmHeight, 0, mmSize, Bitmap->Data + mmOffset);
				} else {
				    // Compressed textures not supported?
					if (Log)
						Log->Report ("ERROR! glBuild2D: GL_ARB_texture_compression is not supported..\n");
					return ID;
				}
			} else {
                // Uploading uncompressed textures
				if ((Bitmap->Flags & BITMAP_RGBA) != 0)
					glTexImage2D (GL_TEXTURE_2D, i, Bitmap->BitsPerPixel / 8, mmWidth, mmHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, Bitmap->Data + mmOffset);
				else if ((Bitmap->Flags & BITMAP_GRAYSCALE) != 0)
					glTexImage2D (GL_TEXTURE_2D, i, Bitmap->BitsPerPixel / 8, mmWidth, mmHeight, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, Bitmap->Data + mmOffset);
				else
					glTexImage2D (GL_TEXTURE_2D, i, Bitmap->BitsPerPixel / 8, mmWidth, mmHeight, 0, GL_RGB, GL_UNSIGNED_BYTE, Bitmap->Data + mmOffset);
			}

			mmOffset += mmSize;

			// Half the image size for the next mip-map level...
			mmWidth /= 2;
			mmHeight /= 2;
		}
	} else {
	    G3::MainLog.Report ("Bitmap %d x %d : %d (0x%X)\n", Bitmap->Width, Bitmap->Height, Bitmap->BitsPerPixel, Bitmap->Data);

		// Automatically build the texture / mipmaps
		if ((Bitmap->Flags & BITMAP_RGBA) != 0)
			gluBuild2DMipmaps (GL_TEXTURE_2D, Bitmap->BitsPerPixel / 8, Bitmap->Width, Bitmap->Height, GL_RGBA, GL_UNSIGNED_BYTE, Bitmap->Data);
		else if ((Bitmap->Flags & BITMAP_GRAYSCALE) != 0)
			gluBuild2DMipmaps (GL_TEXTURE_2D, Bitmap->BitsPerPixel / 8, Bitmap->Width, Bitmap->Height, GL_LUMINANCE, GL_UNSIGNED_BYTE, Bitmap->Data);
		else
			gluBuild2DMipmaps (GL_TEXTURE_2D, Bitmap->BitsPerPixel / 8, Bitmap->Width, Bitmap->Height, GL_RGB, GL_UNSIGNED_BYTE, Bitmap->Data);

        glErrCheck (Log, "gluBuild2DMipmaps", FILE_LINE);
	}

	if (!BitmapLocked)
		ReleaseBitmap ();

	return ID;
}

bool G3::TEXTURE::LoadImage (const std::string &aFilename) {
	std::string fPath = MainFilesys.GetPath (aFilename.c_str ());
	STREAM *pStream = NULL;
	bool ret = false;

	if (fPath.size () > 1) {
		pStream = MainFileHandler.ReadWholeFile (fPath.c_str ());

		if (pStream) {
		    ret = LoadImage (pStream, aFilename);
		    delete pStream;
			return ret;
		}

		delete pStream;
	}
	return false;
}

//==============================================================================
// Frees the texture
//==============================================================================
void G3::TEXTURE::Release () {
	ReleaseBitmap ();
	ReleaseFromCard ();
}

void G3::TEXTURE::ReleaseBitmap () {
	if (Bitmap && Bitmap->IsValid ())
		Bitmap->Destroy ();
}

void G3::TEXTURE::ReleaseFromCard () {
	uint ID2 = (uint) ID;
	glDeleteTextures (1, &ID2);
}

//==============================================================================
// Reloads the texture
//==============================================================================
bool G3::TEXTURE::Recreate () {
	if (LoadImage (Name)) {
		glBuild2D (Filter);
		return true;
	}
	return false;
}

//==============================================================================
// Gets the approximate size of the texture when loaded
//==============================================================================
long G3::TEXTURE::GetSize () {
    // NOTE:: No file's loaded yet - Name is empty
    // Just an approximation of a 32 bit 512x512 texture
	return (sizeof (BITMAP) + sizeof (TEXTURE) + 4 * 512 * 512);
}

//==============================================================================
// Gets the texture dimensions
//==============================================================================
dword G3::TEXTURE::GetWidth () {
	if (Bitmap)
		return Bitmap->Width;
	return 0;
}

dword G3::TEXTURE::GetHeight () {
	if (Bitmap)
		return Bitmap->Height;
	return 0;
}

//==============================================================================
// Gets the texture flags
//==============================================================================
byte G3::TEXTURE::GetFlags () {
	if (Bitmap)
		return Bitmap->Flags;
	return 0;
}

//==============================================================================
// Clears everything
//==============================================================================
void G3::TEXTURE::Clear () {
	// Release already cleared everything
}
